package com.zxy.Answer.scoring;

import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.zxy.Answer.constant.PromptConstant;
import com.zxy.Answer.manager.AiManager;
import com.zxy.Answer.model.dto.question.QuestionAnswerDTO;
import com.zxy.Answer.model.dto.question.QuestionContentDTO;
import com.zxy.Answer.model.entity.App;
import com.zxy.Answer.model.entity.Question;
import com.zxy.Answer.model.entity.ScoringResult;
import com.zxy.Answer.model.entity.UserAnswer;
import com.zxy.Answer.model.vo.QuestionVO;
import com.zxy.Answer.service.QuestionService;
import com.zxy.Answer.service.ScoringResultService;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

/**
 * 评分策略接口
 *  todo 扩展思路
 *      关联上下文，排除重复题目生成
 *      测评类：给定属性集，生成题目选项对应的属性
 *      得分类：给定得分范围，生成题目选项对应的得分。
 */
public interface ScoringStrategy {
    /**
     * @param choices 选项
     * @param app     对应的应用名称
     * @return 对应的得分结果
     */
    UserAnswer doScore(List<String> choices, App app);
}

/**
 * 自定义测评类应用评分策略
 *
 * @author zxy
 * @since 2024/6/4
 **/
@ScoringStrategyConfig(appType = 1, scoringStrategy = 0)
class CustomTextScoringStrategy implements ScoringStrategy {
    @Resource
    private QuestionService qService;
    @Resource
    private ScoringResultService sRService;

    @Override
    public UserAnswer doScore(List<String> choices, App app) {
        // 1、根据id查询题目和题目结果信息
        Long appId = app.getId();
        Question q = qService.getOne(Wrappers
                .lambdaQuery(Question.class).eq(Question::getAppId, appId));
        List<ScoringResult> sRList = sRService.list(
                Wrappers.lambdaQuery(
                        ScoringResult.class).eq(ScoringResult::getAppId, appId)
        );

        // 2、统计用户每个选择对应的属性个数。如 I=10,E=5;
        // 用map统计每个选项的计数
        HashMap<String, Integer> optionCount = new HashMap<>();

        QuestionVO qVo = QuestionVO.objToVo(q);
        List<QuestionContentDTO> qcList = qVo.getQuestionContent();
        // 遍历题目列表
        for (QuestionContentDTO qcDTO : qcList) {
            // 遍历答案列表
            for (String choice : choices) {
                // 遍历题目中的选项
                for (QuestionContentDTO.Option option : qcDTO.getOptions()) {
                    // 答案和选项key匹配
                    if (option.getKey().equals(choice)) {
                        String result = option.getResult();
                        // 属性不存在则，默认为0
                        if (optionCount.containsKey(result)) {
                            optionCount.put(result, 0);
                        }
                        // 计数+1
                        optionCount.put(result, optionCount.get(result) + 1);
                    }
                }
            }
        }

        // 3、遍历每种评分结果，计算那种评分结果最高
        int maxScore = 0;
        ScoringResult maxScoringResult = sRList.get(0);
        for (ScoringResult scoringResult : sRList) {
            List<String> resultProps = JSONUtil.toList(scoringResult.getResultProp(), String.class);
            int score = resultProps.stream()
                    .mapToInt(prop -> optionCount.getOrDefault(prop, 0))
                    .sum();
            // 显示所有评分结果中的最高分
            if (score > maxScore) {
                maxScore = score;
                maxScoringResult = scoringResult;
            }
        }

        // 4、返回最高得分对象
        UserAnswer userAnswer = new UserAnswer();
        userAnswer.setAppId(appId);
        userAnswer.setAppType(app.getAppType());
        userAnswer.setScoringStrategy(app.getScoringStrategy());
        userAnswer.setChoices(JSONUtil.toJsonStr(choices));
        userAnswer.setResultId(maxScoringResult.getId());
        userAnswer.setResultName(maxScoringResult.getResultName());
        userAnswer.setResultDesc(maxScoringResult.getResultDesc());
        userAnswer.setResultPicture(maxScoringResult.getResultPicture());
        return userAnswer;
    }
}

/**
 * 自定义得分类应用评分策略
 *
 * @author zxy
 * @since 2024/6/4
 **/
@ScoringStrategyConfig(appType = 0, scoringStrategy = 0)
class CustomScoreScoringStrategy implements ScoringStrategy {
    @Resource
    private QuestionService qService;
    @Resource
    private ScoringResultService sRService;

    @Override
    public UserAnswer doScore(List<String> choices, App app) {
        // 1、根据id查询题目和题目结果信息（按分数降序排序）
        Long appId = app.getId();
        Question q = qService.getOne(Wrappers.lambdaQuery(Question.class)
                .eq(Question::getAppId, appId)
        );
        // 结果集
        List<ScoringResult> sRList = sRService.list(
                Wrappers.lambdaQuery(ScoringResult.class)
                        .eq(ScoringResult::getAppId, appId)
                        .orderByDesc(ScoringResult::getResultScoreRange)
        );

        // 2、统计总得分
        int totalScore = 0;
        QuestionVO qVo = QuestionVO.objToVo(q);
        List<QuestionContentDTO> qcList = qVo.getQuestionContent();
        // 遍历题目列表
        for (QuestionContentDTO qcDTO : qcList) {
            // 遍历答案列表
            for (String choice : choices)
                // 遍历题目中的选项
                for (QuestionContentDTO.Option option : qcDTO.getOptions())
                    // 答案和选项key匹配
                    if (option.getKey().equals(choice)) {
                        totalScore += Optional.of(option.getScore()).orElse(0);
                    }
        }

        // 3、遍历得分结果,找到用户的得分区间，作为最终的结果
        ScoringResult maxscoringResult = sRList.get(0);
        for (ScoringResult scoringResult : sRList) {
            if (totalScore > scoringResult.getResultScoreRange()) {
                maxscoringResult = scoringResult;
                break;
            }
        }

        // 4、构建返回值结果
        UserAnswer uAnswer = new UserAnswer();
        uAnswer.setAppId(appId);
        uAnswer.setAppType(app.getAppType());
        uAnswer.setScoringStrategy(app.getScoringStrategy());
        uAnswer.setChoices(JSONUtil.toJsonStr(choices));
        uAnswer.setResultName(maxscoringResult.getResultName());
        uAnswer.setResultDesc(maxscoringResult.getResultDesc());
        uAnswer.setResultPicture(maxscoringResult.getResultPicture());
        uAnswer.setResultScore(totalScore);
        return uAnswer;
    }
}

/**
 * Ai得分类应用评分策略
 *      自定义结果集：包含：题目，选项
 *          选项：个数，每个选项的分值范围
 *      返回格式：分数+结果名
 *
 * @author zxy
 * @since 2024/6/4
 **/
@ScoringStrategyConfig(appType = 0, scoringStrategy = 1)
class AiScoreScoringStrategy implements ScoringStrategy {
    @Resource
    private QuestionService qService;
    @Resource
    private AiManager aiManager;

    @Override
    public UserAnswer doScore(List<String> choices, App app) {
        // 1、根据id查询题目和题目结果信息
        Long appId = app.getId();
        Question q = qService.getOne(Wrappers
                .lambdaQuery(Question.class).eq(Question::getAppId, appId));

        QuestionVO qVo = QuestionVO.objToVo(q);
        List<QuestionContentDTO> qcList = qVo.getQuestionContent();
        // 封装prompt
        String userMsg = getAiTestScoringUserMsg(app, qcList, choices);

        // ai评分
        String result = aiManager.doSyncGoodRequest(PromptConstant.SCORING_SCORE_PROMPT, userMsg);

        // 截取字符串信息
        int start = result.indexOf("{");
        int end = result.indexOf("}");
        String json = result.substring(start, end + 1);

        // 4、返回最高得分对象
        UserAnswer userAnswer = JSONUtil.toBean(json, UserAnswer.class);
        userAnswer.setAppId(appId);
        userAnswer.setAppType(app.getAppType());
        userAnswer.setScoringStrategy(app.getScoringStrategy());
        userAnswer.setChoices(JSONUtil.toJsonStr(choices));
        return userAnswer;
    }

    /**
     * 用户消息封装
     *
     * @param app
     * @param qcList
     * @param choices
     * @return
     */
    private String getAiTestScoringUserMsg(App app, List<QuestionContentDTO> qcList, List<String> choices) {
        StringBuilder userMsg = new StringBuilder();
        userMsg.append(app.getAppName()).append("\n");
        userMsg.append(app.getAppDesc()).append("\n");
        List<QuestionAnswerDTO> qcDTOList = new ArrayList<>();
        for (int i = 0; i < qcList.size(); i++) {
            QuestionAnswerDTO qaDTO = new QuestionAnswerDTO();
            qaDTO.setTitle(qcList.get(i).getTitle());
            qaDTO.setUserAnswer(choices.get(i));
            qcDTOList.add(qaDTO);
        }
        userMsg.append(JSONUtil.toJsonStr(qcDTOList));
        return userMsg.toString();
    }
}

/**
 * Ai测评类应用评分策略
 *  todo 待完善
 *
 * @author zxy
 * @since 2024/6/4
 **/
@ScoringStrategyConfig(appType = 1, scoringStrategy = 1)
class AiTextStrategy implements ScoringStrategy {
    // 分布式锁key
    private static final String AI_ANSWER_LOCK = "AI_ANSWER_LOCK";
    /**
     * 本地缓存
     */
    private final Cache<String, String> answerCacheMap = Caffeine.newBuilder()
            .initialCapacity(1024) // 容量
            .expireAfterAccess(5L, TimeUnit.MINUTES)
            .build();
    @Resource
    private QuestionService qService;
    @Resource
    private AiManager aiManager;
    @Resource
    private RedissonClient redissonClient;

    @Override
    public UserAnswer doScore(List<String> choices, App app) {
        // 1、应用id+回答记录=密钥key。相同应用相同的回答结果一样
        Long appId = app.getId();
        String jsonStr = JSONUtil.toJsonStr(choices);
        String cacheKey = buildCacheKey(appId, jsonStr);
        String answerJson = answerCacheMap.getIfPresent(cacheKey);
        // 先查缓存，缓存没有，种缓存
        if (StrUtil.isBlank(answerJson)) {
            RLock lock = redissonClient.getLock(AI_ANSWER_LOCK + cacheKey);
            try {
                // 竞争锁，只有一个线程能获取锁并更新缓存
                boolean res = lock.tryLock(3, 15, TimeUnit.SECONDS);
                // 竞争失败则返回
                if (!res) {
                    return null;
                }

                // 查询应用id对应的题目信息
                Question q = qService.getOne(Wrappers
                        .lambdaQuery(Question.class).eq(Question::getAppId, appId));
                QuestionVO qVo = QuestionVO.objToVo(q);
                List<QuestionContentDTO> qcList = qVo.getQuestionContent();
                // 封装prompt
                String userMsg = getAiTestScoringUserMsg(app, qcList, choices);
                // ai评分
                String result = aiManager.doSyncGoodRequest(PromptConstant.SCORING_TEXT_PROMPT, userMsg);
                // 截取字符串信息
                int start = result.indexOf("{");
                int end = result.indexOf("}");
                answerJson = result.substring(start, end + 1);
                // 放入缓存
                answerCacheMap.put(cacheKey, answerJson);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                if (lock != null && lock.isLocked()) {
                    // 很重要的点，自己只能释放自己的锁（分布式错误释放锁问题）
                    if (lock.isHeldByCurrentThread()) lock.unlock();
                }
            }
        }
        // 4、返回测评答案对象
        UserAnswer userAnswer = JSONUtil.toBean(answerJson, UserAnswer.class);
        userAnswer.setAppId(appId);
        userAnswer.setAppType(app.getAppType());
        userAnswer.setScoringStrategy(app.getScoringStrategy());
        userAnswer.setChoices(jsonStr);
        return userAnswer;
    }


    /**
     * 用户消息封装
     *
     * @param app
     * @param qcList
     * @param choices
     * @return
     */
    private String getAiTestScoringUserMsg(App app, List<QuestionContentDTO> qcList, List<String> choices) {
        StringBuilder userMsg = new StringBuilder();
        userMsg.append(app.getAppName()).append("\n");
        userMsg.append(app.getAppDesc()).append("\n");
        List<QuestionAnswerDTO> qcDTOList = new ArrayList<>();
        for (int i = 0; i < qcList.size(); i++) {
            QuestionAnswerDTO qaDTO = new QuestionAnswerDTO();
            qaDTO.setTitle(qcList.get(i).getTitle());
            qaDTO.setUserAnswer(choices.get(i));
            qcDTOList.add(qaDTO);
        }
        userMsg.append(JSONUtil.toJsonStr(qcDTOList));
        return userMsg.toString();
    }

    /**
     * 构建缓存
     *
     * @param appId
     * @param choices
     * @return
     */
    private String buildCacheKey(Long appId, String choices) {
        return DigestUtil.md5Hex(appId + ":" + choices);
    }
}
